import os,sys
import queue
import numpy as np
import tkinter as tk
from tkinter import ttk
import datetime, time, binascii
import threading
from threading import Lock
import matplotlib.pyplot as plt
import serial.tools.list_ports
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
queue_temperature = queue.Queue(maxsize=0)
queue_humidity = queue.Queue(maxsize=0)
queue_time = queue.Queue(maxsize=0)
global ser
global window, tempGraphLabel, Text_Show
i = 0
obsX = []
obsY1 = []
obsY2 = []
runFlag = True
serial_state = 0
read_flag = 0
save_flag = 0
temperature = 0
humidity = 0
thread_Id = 0
file_sequence = 1
thread_object = None
port_serial = " "
bit_rate_serial = " "
lock = Lock()

"""
串口号改变回掉函数
"""


def write_data(text_):
    global temperature, humidity, file_sequence
    dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    file_path = r'.\log{0}.txt'.format(file_sequence)
    text_.delete('1.0', 'end')
    text_.insert('end', file_path)
    if os.path.exists(file_path):
        file_size = os.stat(file_path).st_size
        # 默认文件大小为120000，文件就满了
        if file_size >= 0 and file_size < 120000:
            with open(file_path, 'a', encoding='UTF-8') as f:
                f.write('\n' + dt + f'  temperature:{temperature}℃, humidity:{humidity}%RH')
        else:
            # print('log' + str(file_sequence) + '.txt 已被写满')
            text_.delete('1.0', 'end')
            text_.insert('end', 'log{0}.txt已被写满'.format(file_sequence))
            file_sequence += 1
            file_path = r'.\log{0}.txt'.format(file_sequence)
            with open(file_path, 'a', encoding='UTF-8') as f:
                f.write('\n' + dt + f'  temperature:{temperature}℃, humidity:{humidity}%RH')
            # print(file_create_time + ' :log' + str(file_sequence) + '.txt创建成功')
            text_.delete('1.0', 'end')
            # print('文件路径：' + file_path)
            text_.insert('end', 'log{0}.txt创建成功,路径：{1}'.format(file_sequence, file_path))
    else:
        text_.delete('1.0', 'end')
        # print('log' + str(file_sequence) + '.txt 不存在')
        text_.insert('end', 'log{0}.txt 不存在'.format(file_sequence))
        with open(file_path, 'a', encoding='UTF-8') as f:
            f.write('\n' + dt + f'  temperature:{temperature}℃, humidity:{humidity}%RH')
        # print(file_create_time + ' :log' + str(file_sequence) + '.txt创建成功')
        # print('文件路径：' + file_path)
        text_.delete('1.0', 'end')
        text_.insert('end', 'log{0}.txt创建成功,路径：{1}'.format(file_sequence, file_path))


def Combo_Serial_Port_handler(var):
    port_serial = var
    print(__file__, var, port_serial)


"""
串口波特率改变回掉函数
"""


def Combo_Serial_Baud_handler(var):
    bitrate_serial = var
    print(__file__, var, bitrate_serial)


"""
串口数据位改变回掉函数
"""


def Combo_Serial_Bits_handler(var):
    databit_serial = var
    print(__file__, var, databit_serial)


"""
串口校验位改变回掉函数
"""


def Combo_Serial_Varify_handler(var):
    verfybit_serial = var
    print(__file__, var, verfybit_serial)


"""
串口停止位改变回掉函数
"""


def Combo_Serial_Stop_handler(var):
    stopbit_serial = var
    print(__file__, var, stopbit_serial)


def _read_data():
    global ser, lock, temperature, humidity, i
    wait_send_string = bytes.fromhex('FE 03 20 01 00 02 8A 04')
    ser.write(wait_send_string)
    # 停止、等待数据，这一步非常关键。timeout压根没用
    time.sleep(0.1)
    count = ser.inWaiting()
    if count > 0:
        data = ser.read(count)
        if data != b'':
            temp=int(str(binascii.b2a_hex(data))[8:12], 16)
            if temp>200:
                temperature = -(65536 - temp) / 100.0
            else:
                temperature=int(str(binascii.b2a_hex(data))[8:12], 16)/100.0

            humidity = int(str(binascii.b2a_hex(data))[12:16], 16) / 100.0
            queue_temperature.put(temperature)
            queue_humidity.put(humidity)
            queue_time.put(i)
            # print(i)
            i = i + 1


class tempGragh():
    def __init__(self, root):
        self.root = root  # 主窗体
        self.canvas = tk.Canvas()  # 创建一块显示图形的画布
        self.figure = self.create_matplotlib()  # 返回matplotlib所画图形的figure对象
        self.showGraphIn(self.figure)  # 将figure显示在tkinter窗体上面

    # 生成fig
    def create_matplotlib(self):
        # 创建绘图对象f
        font = {
            'family': 'Arial',
            'weight': 'normal'
        }
        f = plt.figure('闻道物联', figsize=(6.4, 4.6), dpi=80, edgecolor='green', frameon=True)
        self.fig11 = plt.subplot(1, 1, 1)
        self.fig11.line11 = self.fig11.plot(obsX, obsY1, '.r-', label='$Temperature$', markersize=1)[0]
        self.fig11.line22 = self.fig11.plot(obsX, obsY2, '.b-', label='$Humidity$', markersize=1)[0]
        y_tick = np.linspace(0, 100, 11)
        plt.yticks(y_tick)
        plt.tick_params(pad=0.03)
        plt.grid(True)
        def setLabel(fig, title, titleColor="red"):
            fig.set_title(title + "Real-Time Temperature & Humidity Change of Map", color=titleColor)  # 设置标题
            fig.set_xlabel('Time[s]')  # 设置x轴标签
            fig.set_ylabel("Temperature[℃]&Humidity[%RH]")  # 设置y轴标签
            fig.set_ylim(0, 100)
        setLabel(self.fig11, "Device ")
        plt.legend(frameon=False, loc='upper left', fontsize='small', prop=font)
        f.tight_layout()
        return f

    '''把fig显示到tkinter'''

    def showGraphIn(self, figure):
        # 把绘制的图形显示到tkinter窗口上
        self.canvas = FigureCanvasTkAgg(figure, self.root)
        self.canvas.draw()  # 以前的版本使用show()方法，matplotlib 2.2之后不再推荐show（）用draw代替，但是用show不会报错，会显示警告
        self.canvas.get_tk_widget().pack(side=tk.TOP)  # , fill=tk.BOTH, expand=1

        # 把matplotlib绘制图形的导航工具栏显示到tkinter窗口上
        toolbar = NavigationToolbar2Tk(self.canvas,
                                       self.root)
        toolbar.update()
        self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    '''更新fig'''

    def updateMeltGraph(self):
        plt.gcf()
        if not queue_time.empty():
            obsX.append(queue_time.get())
        if not queue_temperature.empty():
            obsY1.append(queue_temperature.get())
        if not queue_humidity.empty():
            obsY2.append(queue_humidity.get())
        self.fig11.line11.set_xdata(obsX)
        self.fig11.line11.set_ydata(obsY1)
        self.fig11.line22.set_xdata(obsX)
        self.fig11.line22.set_ydata(obsY2)
        self.fig11.set_xlim([i - 100, i + 10])
        self.fig11.set_ylim([0, 100])
        self.fig11.autoscale_view()
        plt.draw()


def updateWindow():
    global window, tempGraphLabel, runFlag, temperature, humidity, read_flag, save_flag
    if runFlag and read_flag:
        dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        Text_Show.insert('end', '[{0}]收<-- 温度：{1:.2f}℃,湿度：{2:.2f}RH\n'.format(dt, temperature, humidity))
        Text_Show.yview_moveto(1)
        if save_flag:
            write_data(Message_Text)
    tempGraphLabel.updateMeltGraph()
    window.after(60, updateWindow)  # 80ms更新画布


def closeWindow():
    global runFlag
    runFlag = False
    sys.exit()


# 自定义一个thread
class My_thread(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(My_thread, self).__init__(*args, **kwargs)
        # 将running设置为True
        self.pause_event = threading.Event()
        self.exit_event = threading.Event()

    def run(self):
        while not self.exit_event.is_set():
            self.pause_event.wait()
            if self.pause_event.is_set():
                _read_data()
        print("退出线程")

    def pause(self):  # 设置为false让线程阻塞
        self.pause_event.clear()

    def resume(self):  # 设置为true让线程从阻塞中唤醒
        self.pause_event.set()

    def stop(self):  # 用于结束线程
        self.exit_event.set()


"""
打开串口按钮回调函数
"""


def Open_Serial_Button_handler(var, text_, port_, bitrate_, databit_, verifybit_, stopbit_):
    global ser, serial_state, thread_object, thread_Id
    print(__file__, port_, bitrate_, databit_, verifybit_, stopbit_, var.get())
    if var.get() == "打开串口":
        serial_state = 1
        var.set("关闭串口")
        ser = serial.Serial(
            port=port_,
            baudrate=int(bitrate_),
            parity=serial.PARITY_NONE,
            timeout=None,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
        )
        if ser.isOpen():
            text_.delete('1.0', 'end')
            text_.insert('end', '串口：{0}已打开\n'.format(port_))
            thread_object = "Id" + str(thread_Id)
            thread_object = My_thread()
            thread_object.daemon = True
            if not thread_object.is_alive():
                thread_object.start()  # 开启线程
                thread_Id = thread_Id + 1

        else:
            text_.delete('1.0', 'end')
            text_.insert('end', '串口：{0}开启失败\n'.format(port_))
    else:
        serial_state = 0
        text_.delete('1.0', 'end')
        text_.insert('end', '串口：{0}已关闭\n'.format(port_))
        thread_object.stop()  # 结束数据的读取
        time.sleep(0.1)
        var.set("打开串口")
        ser.close()

"""
刷新串口回调函数
"""
def Flush_Serial_Button_handler(comboSerialPort_):
    port_list =list(serial.tools.list_ports.comports())
    port_count=len(port_list)
    serial_com=[]
    for m in range(port_count):
        port_list2=list(port_list[m])
        serial_com.append(port_list2[0])
    serial_com.append("COM0")
    comboSerialPort_['values']=serial_com
    comboSerialPort_.pack(side="left")
    print(serial_com)


"""
开始读取数据回调函数
"""


def Start_Read_Data_Button_handler(text_):
    global read_flag, thread_object
    if serial_state == 1:
        text_.delete('1.0', 'end')
        text_.insert('end', '继续读取数据\n')
        read_flag = 1
        thread_object.resume()  # 将阻塞的线程释放，继续运行
    else:
        text_.delete('1.0', 'end')
        text_.insert('end', '请先打开串口！\n')


"""
停止读取数据回调函数
"""


def Stop_Read_Data_Button_handler(text_):
    global read_flag, thread_object
    if serial_state == 1:
        text_.delete('1.0', 'end')
        text_.insert('end', '停止读取数据\n')
        read_flag = 0
        thread_object.pause()  # 阻塞线程，暂停读取数据
    else:
        text_.delete('1.0', 'end')
        text_.insert('end', '请先打开串口！\n')


""""
清空文本框按钮回调函数
"""


def Delete_Content_Button_handler(text_, data_):
    text_.delete('1.0', 'end')
    text_.insert('end', '开始清除显示框内容！\n')
    data_.delete('1.0', 'end')
    text_.delete('1.0', 'end')
    text_.insert('end', '清除完毕！\n')


"""保存文本数据按钮回到函数"""


def Save_Content_Button_handler(var):
    global save_flag
    if var.get() == '保存文本数据':
        var.set('停止保存数据')
        save_flag = 1
    else:
        var.set('保存文本数据')
        save_flag = 0


def Exit_App_Button_handler(var):
    sys.exit()


if __name__ == '__main__':
    global window
    port_list = list(serial.tools.list_ports.comports())
    port_count = len(port_list)
    serial_com = []
    for m in range(port_count):
        port_list_1 = list(port_list[m])
        serial_com.append(port_list_1[0])
    serial_com.append("COM0")

    window = tk.Tk()
    window.title("闻道物联：温湿度传感器测试  http://www.wendaoiot.com/")
    window.iconphoto(True, tk.PhotoImage(file=r'.\logo.png'))
    Frame_root = tk.Frame(window)
    Frame_left = tk.Frame(Frame_root)
    Frame_right = tk.Frame(Frame_root)
    panedwindow_left = tk.PanedWindow(Frame_left, orient="vertical")
    panedwindow_right = tk.PanedWindow(Frame_right, orient="vertical")
    Frame_serial = tk.LabelFrame(panedwindow_left, text="串口设置")
    Frame_edit = tk.LabelFrame(panedwindow_left, text="文本设置")
    Frame_painter = tk.LabelFrame(panedwindow_right, text="数据显示图")
    Frame_text = tk.LabelFrame(panedwindow_right, text="文本显示")
    panedwindow_left.add(Frame_serial)
    panedwindow_left.add(Frame_edit)
    panedwindow_right.add(Frame_painter)
    panedwindow_right.add(Frame_text)

    Frame_Port = tk.Frame(Frame_serial)
    Frame_Port.pack(side="top")
    Label_serial_number = tk.Label(Frame_Port, text="串口号:", height=3)
    Label_serial_number.pack(side="left")
    varPort = tk.StringVar()
    Combo_Serial_Port = ttk.Combobox(Frame_Port, textvariable=varPort, width=10, height=4, justify="center")
    Combo_Serial_Port['values'] = serial_com
    Combo_Serial_Port.bind("<<ComboboxSelected>>", lambda event: Combo_Serial_Port_handler(var=varPort.get()))
    Combo_Serial_Port.current(0)
    Combo_Serial_Port.pack(side="left")

    Frame_Baud = tk.Frame(Frame_serial)
    Frame_Baud.pack(side="top")
    Label_serial_baud = tk.Label(Frame_Baud, text="波特率:", height=3)
    Label_serial_baud.pack(side="left")
    varBaud = tk.StringVar()
    Combo_Serial_Baud = ttk.Combobox(Frame_Baud, textvariable=varBaud, width=10, height=4, justify="center")
    Combo_Serial_Baud['values'] = ("115200","9600", "19200", "38400" )
    Combo_Serial_Baud.bind("<<ComboboxSelected>>", lambda event: Combo_Serial_Baud_handler(var=varBaud.get()))
    Combo_Serial_Baud.current(0)
    Combo_Serial_Baud.pack(side="left")

    Frame_Bits = tk.Frame(Frame_serial)
    Frame_Bits.pack(side="top")
    Label_serial_Bits = tk.Label(Frame_Bits, text="数据位:", height=3)
    Label_serial_Bits.pack(side="left")
    varBits = tk.StringVar()
    Combo_Serial_Bits = ttk.Combobox(Frame_Bits, textvariable=varBits, width=10, height=4, justify="center")
    Combo_Serial_Bits['values'] = ("8 bit", "7 bit", "6 bit", "5 bit")
    Combo_Serial_Bits.bind("<<ComboboxSelected>>", lambda event: Combo_Serial_Bits_handler(var=varBits.get()))
    Combo_Serial_Bits.current(0)
    Combo_Serial_Bits.pack(side="left")

    Frame_Varify = tk.Frame(Frame_serial)
    Frame_Varify.pack(side="top")
    Label_serial_varify = tk.Label(Frame_Varify, text="校验位:", height=3)
    Label_serial_varify.pack(side="left")
    varVarify = tk.StringVar()
    Combo_Serial_Varify = ttk.Combobox(Frame_Varify, textvariable=varVarify, width=10, height=4, justify="center")
    Combo_Serial_Varify['values'] = ("NONE", "ODD", "EVEN", "SPACE")
    Combo_Serial_Varify.bind("<<ComboboxSelected>>", lambda event: Combo_Serial_Varify_handler(var=varVarify.get()))
    Combo_Serial_Varify.current(0)
    Combo_Serial_Varify.pack(side="left")

    Frame_Stop = tk.Frame(Frame_serial)
    Frame_Stop.pack(side="top")
    Label_serial_Stop = tk.Label(Frame_Stop, text="停止位:", height=3)
    Label_serial_Stop.pack(side="left")
    varStop = tk.StringVar()
    Combo_Serial_Stop = ttk.Combobox(Frame_Stop, textvariable=varStop, width=10, height=4, justify="center")
    Combo_Serial_Stop['values'] = ("1 bit", "1.5 bit", "2 bit")
    Combo_Serial_Stop.bind("<<ComboboxSelected>>", lambda event: Combo_Serial_Stop_handler(var=varStop.get()))
    Combo_Serial_Stop.current(0)
    Combo_Serial_Stop.pack(side="left")

    Frame_Tip_set = tk.Frame(Frame_left)
    Frame_Tip_set.pack(side="bottom")
    Message_Label = tk.Label(Frame_Tip_set, text="消息框:", height=1, bd=2, relief="sunken")
    Message_Label.pack(side="left")
    Message_Text = tk.Text(Frame_Tip_set, width=22, height=1, bd=2, relief="sunken", font="red", state="normal")
    Message_Text.pack(side="left", padx=10)
    tempGraphLabel = tempGragh(Frame_painter)

    sb = tk.Scrollbar(Frame_text, bg='gray', activerelief='raised', bd=2, activebackground='blue')
    sb.pack(side="right", fill='y')
    Text_Show = tk.Text(Frame_text, width=80, height=15, bd=2, relief="sunken", state="normal", yscrollcommand=sb.set)
    Text_Show.pack(side="top")
    sb.config(command=Text_Show.yview)
    font = {
        'family': 'Arial',
        'weight': 'normal'
    }
    Frame_Open_Button_set = tk.Frame(Frame_serial)
    Frame_Open_Button_set.pack(side="top")
    var_serial_state = tk.StringVar()
    var_serial_state.set("打开串口")
    Open_Serial_Button = tk.Button(Frame_Open_Button_set, textvariable=var_serial_state, width=20, height=1)
    Open_Serial_Button.bind("<Button-1>",
                            lambda event: Open_Serial_Button_handler(var=var_serial_state, text_=Message_Text,
                                                                     port_=varPort.get(),
                                                                     bitrate_=varBaud.get(),
                                                                     databit_=varBits.get(),
                                                                     verifybit_=varVarify.get(),
                                                                     stopbit_=varStop.get()))
    Open_Serial_Button.pack(side="left", padx=18, pady=10)

    Frame_Flush_Button_set=tk.Frame(Frame_serial)
    Frame_Flush_Button_set.pack(side="top")
    Flush_Serial_Button= tk.Button(Frame_Flush_Button_set, text="刷新串口", width=20, height=1)
    Flush_Serial_Button.bind("<Button-1>", lambda event: Flush_Serial_Button_handler(comboSerialPort_=Combo_Serial_Port))
    Flush_Serial_Button.pack(side="left", padx=18, pady=10)

    Frame_Start_Button_set = tk.Frame(Frame_serial)
    Frame_Start_Button_set.pack(side="top")
    Start_Read_Data_Button = tk.Button(Frame_Start_Button_set, text="开始读取", width=20, height=1)
    Start_Read_Data_Button.bind("<Button-1>", lambda event: Start_Read_Data_Button_handler(text_=Message_Text))
    Start_Read_Data_Button.pack(side="left", padx=18, pady=10)

    Frame_Stop_Button_set = tk.Frame(Frame_serial)
    Frame_Stop_Button_set.pack(side="top")
    Stop_Read_Data_Button = tk.Button(Frame_Stop_Button_set, text="停止读取", width=20, height=1)
    Stop_Read_Data_Button.bind("<Button-1>", lambda event: Stop_Read_Data_Button_handler(text_=Message_Text))
    Stop_Read_Data_Button.pack(side="left", padx=18, pady=10)

    Frame_Delete_Button = tk.Frame(Frame_edit)
    Frame_Delete_Button.pack(side="top")
    Delete_Content_Button = tk.Button(Frame_Delete_Button, text="清空文本内容", width=20, height=1)
    Delete_Content_Button.bind("<Button-1>",
                               lambda event: Delete_Content_Button_handler(text_=Message_Text, data_=Text_Show))
    Delete_Content_Button.pack(side="left", padx=30, pady=10)

    Frame_Save_Button = tk.Frame(Frame_edit)
    Frame_Save_Button.pack(side="top")
    varSaveState = tk.StringVar()
    varSaveState.set("保存文本数据")
    Save_Content_Button = tk.Button(Frame_Save_Button, textvariable=varSaveState, width=20, height=1)
    Save_Content_Button.bind("<Button-1>", lambda event: Save_Content_Button_handler(var=varSaveState))
    Save_Content_Button.pack(side="right", padx=30, pady=10)

    Frame_Exit_App_Button = tk.Frame(Frame_edit)
    Frame_Exit_App_Button.pack(side="top")
    varExitState = tk.StringVar()
    varExitState.set("退出应用界面")
    Exit_App_Button = tk.Button(Frame_Exit_App_Button, textvariable=varExitState, width=20, height=1)
    Exit_App_Button.bind("<Button-1>", lambda event: Exit_App_Button_handler(var=varExitState))
    Exit_App_Button.pack(side="right", padx=30, pady=10)

    panedwindow_left.pack(side="left")
    panedwindow_right.pack(side="right")
    Frame_left.pack(side='left')
    Frame_right.pack(side='right')
    Frame_root.pack()
    updateWindow()
    window.mainloop()
